/*
 * Copyright (C) 2010 Lluís Esquerda
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package net.pixel4fun.mytobike.app.webservice;

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;

import net.pixel4fun.mytobike.app.map.HomeOverlay;
import net.pixel4fun.mytobike.app.map.StationOverlay;
import net.pixel4fun.mytobike.app.map.StationOverlayList;
import net.pixel4fun.mytobike.app.model.Station;
import net.pixel4fun.mytobike.app.util.CircleHelper;

import org.json.JSONArray;
import org.json.JSONObject;

import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.os.Handler;

import com.google.android.maps.GeoPoint;
import com.google.android.maps.MapView;

public class StationsDBAdapter implements Runnable {
	public static final String CENTER_LAT_KEY = "sCenterLat";
	public static final String CENTER_LNG_KEY = "sCenterLng";
	public static final String RADIUS_KEY = "sRadius";
	public static final String VIEW_ALL_KEY = "sViewAll";
	public static final String PREF_NAME = "citybikes";
	

	public static final int FETCH = 0;
	public static final int UPDATE_MAP = 1;
	public static final int UPDATE_MAP_LESS = 2;
	public static final int UPDATE_DATABASE = 3;
	public static final int NETWORK_ERROR = 4;
	public static final String TIMESTAMP_FORMAT = "HH:mm:ss dd/MM/yyyy";

	private StationOverlayList stationsDisplayList;

	private List<StationOverlay> stationsMemoryMap;

	private RESTHelper mRESTHelper;

	private MapView mapView;

	private Context mCtx;

	private Handler handlerOut;

	private Bundle threadData;

	private Queue<Integer> toDo;

	private String RAWstations;

	private String last_updated;

	private GeoPoint center;

	public StationsDBAdapter(Context ctx, MapView mapView, Handler handler,
			StationOverlayList stationsDisplayList) {
		this.mCtx = ctx;
		this.mapView = mapView;
		this.handlerOut = handler;
		this.stationsDisplayList = stationsDisplayList;

		this.mRESTHelper = new RESTHelper(false, null, null);

		this.toDo = new LinkedList<Integer>();
	}

	public StationsDBAdapter(Context ctx, Handler handler) {
		this.mRESTHelper = new RESTHelper(false, null, null);
		this.handlerOut = handler;
		this.toDo = new LinkedList<Integer>();
		this.mCtx = ctx;
	}

	public String fetchStations(String provider) throws Exception {
		return mRESTHelper.restGET(provider);
	}

	public String getLastUpdated() {
		return last_updated;
	}

	public void setCenter(GeoPoint point) {
		this.center = point;
	}

	public void loadStations() throws Exception {
		this.retrieve();
		if (this.center != null)
			buildMemory(new JSONArray(this.RAWstations), this.center);
		else
			buildMemory(new JSONArray(this.RAWstations));
	}

	public void buildMemory(JSONArray stations) throws Exception {
		////Log.i("openBicing", "Building Memory without distances and order");
		this.stationsMemoryMap = new LinkedList<StationOverlay>();
		JSONObject station = null;
		int lat, lng, bikes, free, id;
		String timestamp, name;
		GeoPoint point;
		for (int i = 0; i < stations.length(); i++) {
			station = stations.getJSONObject(i);
			id = station.getInt("id");
			name = station.getString("name");
			lat = Integer.parseInt(station.getString("y"));
			lng = Integer.parseInt(station.getString("x"));
			bikes = station.getInt("B");
			free = station.getInt("F");
			timestamp = station.getString("T");

			point = new GeoPoint(lat, lng);
			Station stat = new Station(id, name, bikes, free, timestamp, mCtx, point);
			StationOverlay memoryStation = new StationOverlay(stat);
			stationsMemoryMap.add(memoryStation);
		}
	}

	public List<StationOverlay> getMemory() throws Exception {
		return stationsMemoryMap;
	}

	public List<StationOverlay> getMemory(int radius) throws Exception {
		List<StationOverlay> res = new LinkedList<StationOverlay>();
		StationOverlay tmp;
		Iterator<StationOverlay> i = stationsMemoryMap.iterator();
		while (i.hasNext()) {
			tmp = i.next();
			if ((tmp.getStation().getMetersDistance() + tmp.getStation().getMetersDistance() * 0.35) <= radius) {
				res.add(tmp);
			}
		}
		return res;
	}

	public void updateDistances(GeoPoint center) {
		Iterator<StationOverlay> i = stationsMemoryMap.iterator();
		while (i.hasNext()) {
			StationOverlay memoryStation = i.next();
			memoryStation.getStation().setMetersDistance(CircleHelper.gp2m(center,
					memoryStation.getCenter()));
			memoryStation.getStation().populateStrings();
		}
	}

	public void buildMemory(JSONArray stations, GeoPoint center)
			throws Exception {
		this.stationsMemoryMap = new LinkedList<StationOverlay>();
		JSONObject station = null;
		int lat, lng, bikes, free, id;
		String timestamp, name;
		GeoPoint point;
		for (int i = 0; i < stations.length(); i++) {
			station = stations.getJSONObject(i);
			id = station.getInt("id");
			name = station.getString("name");
			lat = Integer.parseInt(station.getString("lat"));
			lng = Integer.parseInt(station.getString("lng"));
			bikes = station.getInt("B");
			free = station.getInt("F");
			timestamp = station.getString("T");
			point = new GeoPoint(lat, lng);
			Station stat = new Station(id, name, bikes, free, timestamp, mCtx, point);
			StationOverlay memoryStation = new StationOverlay(stat);
			memoryStation.getStation().setMetersDistance(CircleHelper.gp2m(center, point));
			memoryStation.getStation().populateStrings();
			stationsMemoryMap.add(memoryStation);
		}

		Collections.sort(stationsMemoryMap, new Comparator<Object>() {
			public int compare(Object o1, Object o2) {
				if (o1 instanceof StationOverlay
						&& o2 instanceof StationOverlay) {
					StationOverlay stat1 = (StationOverlay) o1;
					StationOverlay stat2 = (StationOverlay) o2;
					if (stat1.getStation().getMetersDistance() > stat2.getStation().getMetersDistance())
						return 1;
					else
						return -1;
				} else {
					if (o1 instanceof HomeOverlay) {
						return 1;
					} else if (o2 instanceof HomeOverlay) {
						return -1;
					} else {
						return 0;
					}
				}
			}
		});
		////Log.i("openBicing", "Building Memory with distances and order...");
	}

	public void reorder() {
		Collections.sort(stationsMemoryMap, new Comparator<Object>() {
			public int compare(Object o1, Object o2) {
				if (o1 instanceof StationOverlay
						&& o2 instanceof StationOverlay) {
					StationOverlay stat1 = (StationOverlay) o1;
					StationOverlay stat2 = (StationOverlay) o2;
					if (stat1.getStation().getMetersDistance() > stat2.getStation().getMetersDistance())
						return 1;
					else
						return -1;
				} else {
					if (o1 instanceof HomeOverlay) {
						return 1;
					} else if (o2 instanceof HomeOverlay) {
						return -1;
					} else {
						return 0;
					}
				}
			}
		});
	}

	public void store() {
		SharedPreferences settings = this.mCtx.getSharedPreferences(PREF_NAME,
				0);
		SharedPreferences.Editor editor = settings.edit();
		editor.putString("stations", this.RAWstations);
		editor.putString("last_updated", this.last_updated);
		editor.commit();
	}

	public void retrieve() throws Exception {
		SharedPreferences settings = this.mCtx.getSharedPreferences(PREF_NAME,
				0);
		RAWstations = settings.getString("stations", "[]");
		last_updated = settings.getString("last_updated", null);
	}

	public void sync(boolean all, Bundle data) throws Exception {
		this.threadData = data;
		toDo.add(FETCH);
		if (all)
			toDo.add(UPDATE_MAP);
		else
			toDo.add(UPDATE_MAP_LESS);
		toDo.add(UPDATE_DATABASE);
		Thread awesomeThread = new Thread(this);
		awesomeThread.start();
	}

	public void populateStations() throws Exception {
		stationsDisplayList.clear();
		Iterator<StationOverlay> i = stationsMemoryMap.iterator();
		while (i.hasNext()) {
			stationsDisplayList.addStationOverlay(i.next());
		}
		stationsDisplayList.updatePositions();
		mapView.postInvalidate();
	}

	public void populateStations(GeoPoint center, int radius) throws Exception {
		stationsDisplayList.clear();
		Iterator<StationOverlay> i = stationsMemoryMap.iterator();
		this.reorder();
		StationOverlay tmp;
		while (i.hasNext()) {
			tmp = i.next();
			if ((tmp.getStation().getMetersDistance() + tmp.getStation().getMetersDistance() * 0.35) <= radius) {
				stationsDisplayList.addStationOverlay(tmp);
			}
		}
		stationsDisplayList.updatePositions();
		mapView.postInvalidate();
	}

	public void changeMode (boolean getBike){
		Iterator<StationOverlay> i = stationsMemoryMap.iterator();
		while (i.hasNext()){
			Object tmp = i.next();
			if ( tmp instanceof StationOverlay){
				StationOverlay st = (StationOverlay) tmp;
				st.updateStatus(getBike);
			}
		}
	}
	
	@Override
	public void run() {
		while (!toDo.isEmpty()) {
			Integer action = toDo.poll();
			switch (action) {
			case FETCH:
				try {
					SharedPreferences settings = this.mCtx.getSharedPreferences(PREF_NAME,0);
					
					//Get preferences 'filter_stations' checkbox state
					boolean filter_stations = settings.getBoolean("filter_stations", false);

					// Set URL for stations data
					String network_url = "";					
					if (filter_stations)
						network_url = "http://mytobike.appspot.com/stazioni_attive.json";
					else
						network_url = "http://mytobike.appspot.com/stazioni_tutte.json";
					
					//URL value from preferences is not used
					//String network_url = settings.getString("network_url","http://www.pixel4fun.it/mytobike/tobike.json");
					//String network_url = "http://mytobike.appspot.com/stazioni_attive.json";
					
					RAWstations = fetchStations(network_url);
					SimpleDateFormat sdf = new SimpleDateFormat(
							TIMESTAMP_FORMAT);
					Calendar cal = Calendar.getInstance();
					last_updated = sdf.format(cal.getTime());
					buildMemory(new JSONArray(RAWstations), this.center);
				} catch (Exception fetchError) {
					handlerOut.sendEmptyMessage(NETWORK_ERROR);
					fetchError.printStackTrace();
					try {
						retrieve();
						buildMemory(new JSONArray(RAWstations), this.center);
					} catch (Exception internalError) {
						// FUCK EVERYTHING!
					}
				}
				handlerOut.sendEmptyMessage(FETCH);
				break;
			case UPDATE_MAP:
				try {
					populateStations();
				} catch (Exception populateError) {

				}
				handlerOut.sendEmptyMessage(UPDATE_MAP);
				break;
			case UPDATE_MAP_LESS:
				try {
					GeoPoint center = new GeoPoint(threadData
							.getInt(CENTER_LAT_KEY), threadData
							.getInt(CENTER_LNG_KEY));
					populateStations(center, threadData.getInt(RADIUS_KEY));
				} catch (Exception populateError) {

				}
				handlerOut.sendEmptyMessage(UPDATE_MAP);
				break;
			case UPDATE_DATABASE:
				store();
				handlerOut.sendEmptyMessage(UPDATE_DATABASE);
				break;
			}
		}
	}
}
